package com.lambkit.core;

import cn.hutool.core.bean.BeanPath;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.getter.BasicTypeGetter;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.func.Func0;
import cn.hutool.core.lang.func.LambdaUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Time;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;

/**
 * @author yangyong(孤竹行)
 */
public abstract class BaseAttr<M extends BaseAttr> extends LinkedHashMap<String, Object> implements BasicTypeGetter<String>, IAttr<M> {

    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    /**
     * 是否大小写不敏感
     */
    private boolean caseInsensitive;

    // --------------------------------------------------------------- Constructor start

    /**
     * 构造
     */
    public BaseAttr() {
        this(false);
    }

    /**
     * 构造
     *
     * @param caseInsensitive 是否大小写不敏感
     */
    public BaseAttr(boolean caseInsensitive) {
        this(DEFAULT_INITIAL_CAPACITY, caseInsensitive);
    }

    /**
     * 构造
     *
     * @param initialCapacity 初始容量
     */
    public BaseAttr(int initialCapacity) {
        this(initialCapacity, false);
    }

    /**
     * 构造
     *
     * @param initialCapacity 初始容量
     * @param caseInsensitive 是否大小写不敏感
     */
    public BaseAttr(int initialCapacity, boolean caseInsensitive) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR, caseInsensitive);
    }

    /**
     * 构造
     *
     * @param initialCapacity 初始容量
     * @param loadFactor      容量增长因子，0~1，即达到容量的百分之多少时扩容
     */
    public BaseAttr(int initialCapacity, float loadFactor) {
        this(initialCapacity, loadFactor, false);
    }

    /**
     * 构造
     *
     * @param initialCapacity 初始容量
     * @param loadFactor      容量增长因子，0~1，即达到容量的百分之多少时扩容
     * @param caseInsensitive 是否大小写不敏感
     * @since 4.5.16
     */
    public BaseAttr(int initialCapacity, float loadFactor, boolean caseInsensitive) {
        super(initialCapacity, loadFactor);
        this.caseInsensitive = caseInsensitive;
    }

    /**
     * 构造
     *
     * @param m Map
     */
    public BaseAttr(Map<String, Object> m) {
        super((null == m) ? new HashMap<>() : m);
    }
    // --------------------------------------------------------------- Constructor end

    /**
     * 转换为Bean对象
     *
     * @param <T>  Bean类型
     * @param bean Bean
     * @return Bean
     */
    public <T> T toBean(T bean) {
        return toBean(bean, false);
    }

    /**
     * 转换为Bean对象
     *
     * @param <T>  Bean类型
     * @param bean Bean
     * @return Bean
     * @since 3.3.1
     */
    public <T> T toBeanIgnoreCase(T bean) {
        BeanUtil.fillBeanWithMapIgnoreCase(this, bean, false);
        return bean;
    }

    /**
     * 转换为Bean对象
     *
     * @param <T>           Bean类型
     * @param bean          Bean
     * @param isToCamelCase 是否转换为驼峰模式
     * @return Bean
     */
    public <T> T toBean(T bean, boolean isToCamelCase) {
        BeanUtil.fillBeanWithMap(this, bean, isToCamelCase, false);
        return bean;
    }

    /**
     * 转换为Bean对象,并使用驼峰法模式转换
     *
     * @param <T>  Bean类型
     * @param bean Bean
     * @return Bean
     */
    public <T> T toBeanWithCamelCase(T bean) {
        BeanUtil.fillBeanWithMap(this, bean, true, false);
        return bean;
    }

    /**
     * 填充Value Object对象
     *
     * @param <T>   Bean类型
     * @param clazz Value Object（或者POJO）的类
     * @return vo
     */
    public <T> T toBean(Class<T> clazz) {
        return BeanUtil.toBean(this, clazz);
    }

    /**
     * 填充Value Object对象，忽略大小写
     *
     * @param <T>   Bean类型
     * @param clazz Value Object（或者POJO）的类
     * @return vo
     */
    public <T> T toBeanIgnoreCase(Class<T> clazz) {
        return BeanUtil.toBeanIgnoreCase(this, clazz, false);
    }

    /**
     * 将值对象转换为Dict<br>
     * 类名会被当作表名，小写第一个字母
     *
     * @param <T>  Bean类型
     * @param bean 值对象
     * @return 自己
     */
    public <T> M parseBean(T bean) {
        Assert.notNull(bean, "Bean class must be not null");
        this.putAll(BeanUtil.beanToMap(bean));
        return (M) this;
    }

    /**
     * 将值对象转换为Dict<br>
     * 类名会被当作表名，小写第一个字母
     *
     * @param <T>               Bean类型
     * @param bean              值对象
     * @param isToUnderlineCase 是否转换为下划线模式
     * @param ignoreNullValue   是否忽略值为空的字段
     * @return 自己
     */
    public <T> M parseBean(T bean, boolean isToUnderlineCase, boolean ignoreNullValue) {
        Assert.notNull(bean, "Bean class must be not null");
        this.putAll(BeanUtil.beanToMap(bean, isToUnderlineCase, ignoreNullValue));
        return (M) this;
    }

    /**
     * 与给定实体对比并去除相同的部分<br>
     * 此方法用于在更新操作时避免所有字段被更新，跳过不需要更新的字段 version from 2.0.0
     *
     * @param <T>          字典对象类型
     * @param dict         字典对象
     * @param withoutNames 不需要去除的字段名
     */
    public <T extends M> void removeEqual(T dict, String... withoutNames) {
        HashSet<String> withoutSet = CollUtil.newHashSet(withoutNames);
        Set<Map.Entry<String, Object>> entries = dict.entrySet();
        for (Map.Entry<String, Object> entry : entries) {
            if (withoutSet.contains(entry.getKey())) {
                continue;
            }

            final Object value = this.get(entry.getKey());
            if (Objects.equals(value, entry.getValue())) {
                this.remove(entry.getKey());
            }
        }
    }


    public <T> T link(String keyLink) {
        Object res = null;
        if(StrUtil.isNotBlank(keyLink)) {
            String[] keys = keyLink.trim().split("\\.");
            if(keys.length > 0) {
                Object obj = this;
                for (int i=0; i< keys.length; i++) {
                    String skey = keys[i];
                    if(obj != null) {
                        obj = linkey(obj, skey);
                    }
                }
                res = obj;
            }
        }
        return (T) res;
    }

    private Object linkey(Object data, String key) {
        if(data instanceof Map) {
            Map<String, Object> map = (Map<String, Object>)data;
            return map.get(key);
        } else if(data instanceof List) {
            List<Object> list = (List<Object>)data;
            int i = NumberUtil.parseInt(key, -1);
            if(i>=0 && i<list.size()) {
                return list.get(i);
            }
        } else if(data instanceof Object[]) {
            Object[] array = (Object[])data;
            int i = NumberUtil.parseInt(key, -1);
            if(i>=0 && i<array.length) {
                return array[i];
            }
        } else if(data instanceof BaseAttr) {
            return ((BaseAttr)data).get(key);
        }
        return null;
    }
    // -------------------------------------------------------------------- Set start

    /**
     * 设置列
     *
     * @param attr  属性
     * @param value 值
     * @return 本身
     */
    public M set(String attr, Object value) {
        this.put(attr, value);
        return (M) this;
    }

    /**
     * 设置列，当键或值为null时忽略
     *
     * @param attr  属性
     * @param value 值
     * @return 本身
     */
    public M setIgnoreNull(String attr, Object value) {
        if (null != attr && null != value) {
            set(attr, value);
        }
        return (M) this;
    }

    @Override
    public M put(Map<String, Object> otherMap) {
        putAll(otherMap);
        return (M) this;
    }

    // -------------------------------------------------------------------- Set end

    // -------------------------------------------------------------------- Get start

    @Override
    public Object getObj(String key) {
        return super.get(key);
    }

    public Object getObject(String key) {
        return super.get(key);
    }

    /**
     * 获得特定类型值
     *
     * @param <T>  值类型
     * @param attr 字段名
     * @return 字段值
     * @since 4.6.3
     */
    public <T> T getBean(String attr) {
        return get(attr, null);
    }

    /**
     * 获得特定类型值
     *
     * @param <T>          值类型
     * @param attr         字段名
     * @param defaultValue 默认值
     * @return 字段值
     */
    @SuppressWarnings("unchecked")
    public <T> T get(String attr, T defaultValue) {
        final Object result = super.get(attr);
        return (T) (result != null ? result : defaultValue);
    }

    /**
     * @param attr 字段名
     * @return 字段值
     */
    @Override
    public String getStr(String attr) {
        return Convert.toStr(get(attr), null);
    }

    /**
     * @param attr 字段名
     * @return 字段值
     */
    @Override
    public Integer getInt(String attr) {
        return Convert.toInt(get(attr), null);
    }

    /**
     * @param attr 字段名
     * @return 字段值
     */
    @Override
    public Long getLong(String attr) {
        return Convert.toLong(get(attr), null);
    }

    /**
     * @param attr 字段名
     * @return 字段值
     */
    @Override
    public Float getFloat(String attr) {
        return Convert.toFloat(get(attr), null);
    }

    @Override
    public Short getShort(String attr) {
        return Convert.toShort(get(attr), null);
    }

    @Override
    public Character getChar(String attr) {
        return Convert.toChar(get(attr), null);
    }

    @Override
    public Double getDouble(String attr) {
        return Convert.toDouble(get(attr), null);
    }

    @Override
    public Byte getByte(String attr) {
        return Convert.toByte(get(attr), null);
    }

    /**
     * @param attr 字段名
     * @return 字段值
     */
    @Override
    public Boolean getBool(String attr) {
        return Convert.toBool(get(attr), null);
    }

    /**
     * @param attr 字段名
     * @return 字段值
     */
    @Override
    public BigDecimal getBigDecimal(String attr) {
        return Convert.toBigDecimal(get(attr));
    }

    /**
     * @param attr 字段名
     * @return 字段值
     */
    @Override
    public BigInteger getBigInteger(String attr) {
        return Convert.toBigInteger(get(attr));
    }

    @Override
    public <E extends Enum<E>> E getEnum(Class<E> clazz, String key) {
        return Convert.toEnum(clazz, get(key));
    }

    /**
     * @param attr 字段名
     * @return 字段值
     */
    public byte[] getBytes(String attr) {
        return get(attr, null);
    }

    /**
     * @param attr 字段名
     * @return 字段值
     */
    @Override
    public Date getDate(String attr) {
        return get(attr, null);
    }

    /**
     * @param attr 字段名
     * @return 字段值
     */
    public Time getTime(String attr) {
        return get(attr, null);
    }

    /**
     * @param attr 字段名
     * @return 字段值
     */
    public Timestamp getTimestamp(String attr) {
        return get(attr, null);
    }

    /**
     * @param attr 字段名
     * @return 字段值
     */
    public Number getNumber(String attr) {
        return get(attr, null);
    }

    /**
     * 通过表达式获取JSON中嵌套的对象<br>
     * <ol>
     * <li>.表达式，可以获取Bean对象中的属性（字段）值或者Map中key对应的值</li>
     * <li>[]表达式，可以获取集合等对象中对应index的值</li>
     * </ol>
     * <p>
     * 表达式栗子：
     *
     * <pre>
     * persion
     * persion.name
     * persons[3]
     * person.friends[5].name
     * </pre>
     *
     * @param <T>        目标类型
     * @param expression 表达式
     * @return 对象
     * @see BeanPath#get(Object)
     * @since 5.7.14
     */
    @SuppressWarnings("unchecked")
    public <T> T getByPath(String expression) {
        return (T) BeanPath.create(expression).get(this);
    }

    /**
     * 通过表达式获取JSON中嵌套的对象<br>
     * <ol>
     * <li>.表达式，可以获取Bean对象中的属性（字段）值或者Map中key对应的值</li>
     * <li>[]表达式，可以获取集合等对象中对应index的值</li>
     * </ol>
     * <p>
     * 表达式栗子：
     *
     * <pre>
     * persion
     * persion.name
     * persons[3]
     * person.friends[5].name
     * </pre>
     * <p>
     * 获取表达式对应值后转换为对应类型的值
     *
     * @param <T>        返回值类型
     * @param expression 表达式
     * @param resultType 返回值类型
     * @return 对象
     * @see BeanPath#get(Object)
     * @since 5.7.14
     */
    public <T> T getByPath(String expression, Class<T> resultType) {
        return Convert.convert(resultType, getByPath(expression));
    }

    @Override
    public <T> T get(String key) {
        final Object result = super.get(key);
        return (T) (result != null ? result : null);
    }

    @Override
    public LocalDateTime getLocalDateTime(String key) {
        return Convert.toLocalDateTime(this.get(key));
    }

    @Override
    public Boolean getBoolean(String key) {
        return getBool(key);
    }

    // -------------------------------------------------------------------- Get end

    @Override
    public boolean containsKey(Object key) {
        return super.containsKey(customKey((String) key));
    }

    @Override
    public Object get(Object key) {
        return super.get(customKey((String) key));
    }

    @Override
    public Object put(String key, Object value) {
        return super.put(customKey(key), value);
    }

    @Override
    public void putAll(Map<? extends String, ?> m) {
        m.forEach(this::put);
    }

    @Override
    public BaseAttr clone() {
        return (BaseAttr) super.clone();
    }

    @Override
    public Object remove(Object key) {
        return super.remove(customKey((String) key));
    }

    @Override
    public boolean remove(Object key, Object value) {
        return super.remove(customKey((String) key), value);
    }

    @Override
    public boolean replace(String key, Object oldValue, Object newValue) {
        return super.replace(customKey(key), oldValue, newValue);
    }

    @Override
    public Object replace(String key, Object value) {
        return super.replace(customKey(key), value);
    }

    //---------------------------------------------------------------------------- Override default methods start
    @Override
    public Object getOrDefault(Object key, Object defaultValue) {
        return super.getOrDefault(customKey((String) key), defaultValue);
    }

    @Override
    public Object computeIfPresent(final String key, final BiFunction<? super String, ? super Object, ?> remappingFunction) {
        return super.computeIfPresent(customKey(key), remappingFunction);
    }

    @Override
    public Object compute(final String key, final BiFunction<? super String, ? super Object, ?> remappingFunction) {
        return super.compute(customKey(key), remappingFunction);
    }

    @Override
    public Object merge(final String key, final Object value, final BiFunction<? super Object, ? super Object, ?> remappingFunction) {
        return super.merge(customKey(key), value, remappingFunction);
    }

    @Override
    public Object putIfAbsent(String key, Object value) {
        return super.putIfAbsent(customKey(key), value);
    }

    @Override
    public Object computeIfAbsent(String key, Function<? super String, ?> mappingFunction) {
        return super.computeIfAbsent(customKey(key), mappingFunction);
    }

    //---------------------------------------------------------------------------- Override default methods end

    /**
     * 将Key转为小写
     *
     * @param key KEY
     * @return 小写KEY
     */
    private String customKey(String key) {
        if (this.caseInsensitive && null != key) {
            key = key.toLowerCase();
        }
        return key;
    }

    /**
     * 通过lambda批量设置值<br>
     * 实际使用时，可以使用getXXX的方法引用来完成键值对的赋值：
     * <pre>
     *     User user = GenericBuilder.of(User::new).with(User::setUsername, "hutool").build();
     *     Attri.create().setFields(user::getNickname, user::getUsername);
     * </pre>
     *
     * @param fields lambda,不能为空
     * @return this
     * @since 5.7.23
     */
    public M setFields(Func0<?>... fields) {
        Arrays.stream(fields).forEach(f -> set(LambdaUtil.getFieldName(f), f.callWithRuntimeException()));
        return (M) this;
    }

    @Override
    public Map<String, Object> toMap() {
        return this;
    }

    @Override
    public void removeNullValueAttrs() {
        this.entrySet().removeIf(entry -> entry.getValue() == null);
    }
}
